<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <!-- element-plus style -->
    <link rel="stylesheet" href="//unpkg.com/element-plus/dist/index.css"/>
    <!-- Vue 3 -->
    <script src="//unpkg.com/vue@3"></script>
    <!-- element-plus -->
    <script src="//unpkg.com/element-plus"></script>
    <!-- md5.js -->
    <script src="https://cdn.jsdelivr.net/gh/emn178/js-md5/build/md5.min.js"></script>
    <!-- axios -->
    <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
</head>
<body>
<div id="app">
    <el-upload
            :on-success="clearFileList"

            drag
            :file-list="fileList"
            :auto-upload="false"
            @change="handleFileChange"
    >
        <div class="el-upload__text">
            点击或拖拽上传
        </div>
    </el-upload>
    <div style="text-align: center;margin-bottom: 20px">
        <el-button type="primary" @click="uploadFilesList">上传</el-button>
    </div>

    <div>
        <el-progress
                :text-inside="true"
                :stroke-width="20"
                :percentage="successfulChunkPercents "
                status="success"
        />
    </div>

    <!--    已上传列表-->
    <el-table
            :data="uploadList"
            border
            style="width: 100%">
        <el-table-column
                fixed
                prop="id.date"
                label="日期"
                width="150">
        </el-table-column>
        <el-table-column
                prop="url"
                label="下载地址"
        >
        </el-table-column>

        <el-table-column
                label="操作"
        >
            <template #default="scope">
                <el-button
                        link
                        type="primary"
                        size="small"
                        @click.prevent="deleteFile(scope.row.url)"
                >
                    删除
                </el-button>
                <el-button
                        link
                        type="primary"
                        size="small"
                        @click.prevent="downloadFile(scope.row.url)"
                >
                    下载
                </el-button>
            </template>
        </el-table-column>
    </el-table>
</div>
<script>
    const App = {
        data() {
            return {
                currentFile: null, // 当前选中的文件
                chunkSize: 6 * 1024 * 1024, // 分片大小:超过5MB开始分片，minio要求分块大小必须>5M
                chunkCount: 0, // 分片数量
                chunksUploaded: 0, // 已上传的分片数量
                fileMd5: '', // 文件的md5值
                fileSuffix: '', // 文件后缀
                successfulChunkPercents: 0, // 上传成功的分片百分比
                chunkList: [], // 分片列表
                uploadList: [], // 已上传列表
                fileList: [], // 文件列表

                // 完成多文件分片上传
                uploadFileList: [], // 使用对象数组，对象属性值包括(md5哈希值+切块数组)
                uploadChunks: 0, // 单个文件分块的数量
                allChunks: 0 //单个文件进度
            };
        },

        methods: {


            // 异步方法：选择文件时触发 ============
            async handleFileChange(file) {
                this.successfulChunkPercents = 0;
                const reader = new FileReader()
                reader.readAsArrayBuffer(file.raw); // 异步读取文件内容

                const fileContent = await new Promise((resolve, reject) => {
                    reader.onload = (event) => {
                        console.log('开始读取文件了...')
                        resolve(event.target.result)
                    };
                    reader.onerror = (error) => {
                        console.log('读取文件时发生错误...', error)
                        reject(error)
                    }
                })

                // 计算MD5哈希值
                this.fileMd5 = md5(fileContent).toString();
                console.log('111文件的md5哈希值是：', this.fileMd5)

                console.log('file对象的大小：', file.size)
                this.chunkCount = Math.ceil(file.size / this.chunkSize); // 还是计算分块数量，向上取整
                this.chunksUploaded = 0; // 已上传的分片数量
                this.fileSuffix = "." + file.raw.name.split('.').pop() // 得到.文件类型
                this.successfulChunkPercents = 0 // 上传成功的分片百分比
                this.currentFile = await new Promise((resolve, reject) => {
                    console.log('====开始获取File对象了...')
                    resolve(file.raw)
                    reject('获取File对象失败')
                })
                for (let i = 0; i < this.chunkCount; i++) { // 文件开始遍历切片存入数组
                    const start = i * this.chunkSize;
                    console.log('循环中的currentFile:', this.currentFile)
                    const end = Math.min(start + this.chunkSize - 1, this.currentFile.size - 1);
                    this.chunkList[i] = this.currentFile.slice(start, end)
                }
                console.log('md5:', this.fileMd5)
                console.log('chunkList:', this.chunkList)
                console.log('fileSuffix:', this.fileSuffix)
                this.uploadFileList.push(
                    {
                        md5: this.fileMd5, // md5哈希值
                        chunks: this.chunkList, // 分块列表
                        fileSuffix: this.fileSuffix // 后缀
                    }
                ); // 使用对象数组进行处理 ============
                this.chunkList = []
                console.log('uploadFileList是：', this.uploadFileList)
                console.log('结束handleFileChange了')
            },


            // 点击上传按钮触发多文件上传 ===============
            async uploadFilesList() {
                if (this.currentFile == null) {
                    alert("请选择文件后再上传！")
                    this.successfulChunkPercents = 0; // 重置百分比
                    this.fileList = []; // 文件列表
                    return
                }
                // 检查所有文件中是否存在未上传的，未上传则需要上传对应的文件 =========
                let md5List = [];
                for(let i = 0; i < this.uploadFileList.length; i++) {
                    let element = {
                        md5: this.uploadFileList[i].md5,
                        fileSuffix: this.uploadFileList[i].fileSuffix
                    }
                    md5List.push(element)
                }
                console.log('上传的md5_suffix_List是：', md5List)


                await axios.post("http://localhost:8084/minio/checkMd5List", md5List)
                    .then(res => {
                        console.log('文件上传返回结果：', res.data)

                        var list = res.data;
                        if (list.length !== 0) {
                            let upList = []
                            for (let item1 of this.uploadFileList) {
                                for(let item2 of list) {
                                    if (item1.md5 === item2) {
                                        upList.push(item1)
                                    }
                                }
                            }
                            console.log('upList是：', upList)
                            this.uploadFileList = upList;
                        } else {
                            this.clearFileList();
                        }
                        console.log("最后必须上传的文件：", this.uploadFileList)
                        // 文件均存在minio中了，无需上传
                        if (this.uploadFileList.length === 0) {
                            this.successfulChunkPercents = 100;
                            alert('文件上传成功')
                        }
                    })
                    .catch(error => {
                        console.log('检查返回错误', error)
                    })

                console.log("开始上传", this.uploadFileList)
                // 检查上传的多个文件是否均存在，如果部分存在，进行剔除，剩余部分仍旧进行上传。
                // 分块的Promises化
                const chunkPromises = [];
                // 上传分块的数组，校验是否完成上传
                const chunksUploadedList = [];
                // 直接计算一共多少个分块
                this.uploadFileList.forEach(item => {
                    this.allChunks += item.chunks.length
                })

                console.log('所有文件加起来一共有多少个分块？', this.allChunks)
                for (const item of this.uploadFileList) {
                    const md5 = item.md5;
                    for (let i = 0; i < item.chunks.length; i++) {
                        console.log('上传第', i+1, '个分片')
                        let chunk = item.chunks[i];
                        chunkPromises.push(await this.uploadFilesChunk(md5, chunk, i+1, () => {
                            this.chunksUploaded++;
                            this.uploadChunks++;
                            console.log('执行了自增...')
                            this.successfulChunkPercents = 100 * (this.uploadChunks / this.allChunks).toFixed(2);
                            console.log('this.uploadChunks:', this.uploadChunks)
                            console.log('this.allChunks:', this.allChunks)
                            console.log('进度：', (this.uploadChunks / this.allChunks).toFixed(2))

                        }))
                    }
                    console.log('this.chunkUploaded是：', this.chunksUploaded)
                    chunksUploadedList.push(this.chunksUploaded); // 存储不同文件的上传分块数量
                    this.chunksUploaded = 0;
                }
                await Promise.all(chunkPromises);
                console.log('上传完成！')
                console.log('chunksUploadList是：', chunksUploadedList)
                let mergeResults = [];
                for (let i = 0; i < chunksUploadedList.length; i++) {
                    console.log('this.uploadFileList' + i + '是：' + this.uploadFileList[i].chunks.length)
                    console.log('chunksUploadedList' + i + '是：' + chunksUploadedList[i])
                    if (this.uploadFileList[i].chunks.length === chunksUploadedList[i]) {
                        const mergeResult = await axios.post(`http://localhost:8084/minio/merge?md5=${this.uploadFileList[i].md5}&fileSuffix=${this.uploadFileList[i].fileSuffix}&chunkTotal=${chunksUploadedList[i]}`)
                        if (mergeResult.data.startsWith("[miss_chunk]")) {
                            alert('文件缺失，请重新上传')
                            return
                        }
                        console.log("合并结果:", mergeResult)
                        mergeResults.push(mergeResult)
                    }
                }
                let finalRes = true;
                for (const result of mergeResults) {
                    if (result.data === '失败') {
                        finalRes = false
                        alert("上传失败！请重新上传")
                        return
                    } else {
                        alert("上传成功！")
                        this.successfulChunkPercents = 100
                        this.clearFileList()
                        this.getList()
                        return
                    }
                }

            },




            // 多文件上传分片 ============
            async uploadFilesChunk(md5, chunk, chunkIndex, onSuccess) {
                console.log('进入了uploadFileChunk方法...')
                let retryTime = 5; //重试次数
                const formData = new FormData()
                formData.append('md5', md5)
                formData.append('chunkIndex', chunkIndex);
                formData.append('chunk', chunk);
                return axios.post('http://localhost:8084/minio/upload', formData, {headers: {'Content-Type': 'multipart/form-data'}})
                    .then(res => onSuccess())
                    .catch(error => {
                        if (retryTime > 0) {
                            retryTime--;
                            return this.uploadChunk(md5, chunk, chunkIndex, onSuccess);
                        }
                    });
            },

            // 上传分片 旧
            uploadChunk(chunk, chunkIndex, onSuccess) {
                let retryTime = 5; //重试次数
                const formData = new FormData()
                formData.append('md5', this.fileMd5)
                formData.append('chunkIndex', chunkIndex);
                formData.append('chunk', chunk);
                return axios.post('http://localhost:8084/minio/upload', formData, {headers: {'Content-Type': 'multipart/form-data'}})
                    .then(res => onSuccess())
                    .catch(error => {
                        if (retryTime > 0) {
                            retryTime--;
                            return this.uploadChunk(chunk, chunkIndex, onSuccess);
                        }
                    });
            },

            // 删除文件
            deleteFile(url) {
                axios.get(`http://localhost:8084/minio/delete?url=` + url)
                    .then(res => {
                        console.log('删除文件：', res.data)
                        alert(res.data ? "删除成功！" : '删除失败！')
                        this.getList()
                    })
                    .catch(error => {
                        console.log('删除失败：', error)
                    })
                console.log('url是:', url)
            },

            downloadFile(url) {
                window.location.href = url;
            },

            getList() {
                axios.get("http://localhost:8084/minio/list")
                    .then(res => {
                        this.uploadList = res.data
                        console.log("获取列表结果成功：", res.data)
                    })
                    .catch(error => {
                        console.error("获取列表失败：", error)
                    })
            },

            clearFileList() {
                this.successfulChunkPercents = 0;
                this.uploadFileList = []
                this.fileList = []
            }
        },
        mounted() {
            this.getList();
        }
    };
    const app = Vue.createApp(App);
    app.use(ElementPlus);
    app.mount("#app");

</script>
</body>
</html>